/*Z
老旧设备DMA保留内存映射过程：
    根表(root table)      ->  上下文表(context table)      -> 第二层多级页表(Second-Level Page Table Structures)
    1个4K, 256项              每个4K，256项                   基本同正常页表
    每项代表一条PCI总线       每项代表一个设备的一个功能      每个老旧设备(bus:dev:func)一个独立页表
                                           32   ×    8
                              每项都属于不同的域
*/

/*
 * Copyright 2014, General Dynamics C4 Systems
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#include <config.h>

#ifdef CONFIG_IOMMU

#include <kernel/boot.h>
#include <machine.h>
#include <machine/io.h>
#include <arch/kernel/apic.h>
#include <arch/model/statedata.h>
#include <linker.h>
#include <plat/machine/acpi.h>
#include <plat/machine/intel-vtd.h>
#include <util.h>

#define RTADDR_REG  0x20    /*Z IOMMU根表线性地址寄存器。64位 */
#define GCMD_REG    0x18    /*Z 全局命令寄存器。32位。与全局状态寄存器有一一对应关系 */
#define GSTS_REG    0x1C    /*Z 全局状态寄存器。32位 */
#define CCMD_REG    0x28    /*Z 上下文缓存命令寄存器。64位 */
#define ECAP_REG    0x10    /*Z 扩展能力寄存器。64位 */
#define IOTLB_REG   0x08    /*Z IOTLB失效命令寄存器距失效地址寄存器的偏移 */
#define FSTS_REG    0x34    /*Z 错误状态寄存器。32位 */
#define FECTL_REG   0x38    /*Z 对错误事件中断的控制寄存器。32位 */
#define FEDATA_REG  0x3C    /*Z 错误事件发出的中断消息。32位 */
#define FEADDR_REG  0x40    /*Z 发出错误事件中断的地址(低32位) */
#define FEUADDR_REG 0x44    /*Z 发出错误事件中断的地址(高32位) */
#define CAP_REG     0x08    /*Z IOMMU能力寄存器 */

/* Bit Positions within Registers */
#define SRTP        30  /* Set Root Table Pointer *//*Z IOMMU全局命令寄存器的设置/更新根表指示位 */
#define RTPS        30  /* Root Table Pointer Status *//*Z IOMMU全局状态寄存器的根表设置状态指示。1-完成，0-未完成 */
#define TE          31  /* Translation Enable *//*Z IOMMU全局命令寄存器的DMA重映射使能位 */
#define TES         31  /* Translation Enable Status *//*Z IOMMU全局状态寄存器的DMA重映射标志位 */

/* ICC is 63rd bit in CCMD_REG, but since we will be
 * accessing this register as 4 byte word, ICC becomes
 * 31st bit in the upper 32bit word.
 */
#define ICC         (31 + 32)  /* Invalidate Context Cache *//*Z 失效上下文缓存指示位 */
#define CIRG        (29 + 32) /* Context Invalidation Request Granularity *//*Z 失效上下文缓存的粒度。2位 */
#define CAIG        27  /* Context Actual Invalidation Granularity */
#define CAIG_MASK   0x3
#define IVO_MASK    0x3FF   /*Z 扩展能力寄存器中指示IOTLB失效地址寄存器的位数掩码 */
#define IVT         31  /* Invalidate IOTLB *//*Z IOTLB失效指示位（高32位部分索引）*/
#define IIRG        28  /* IOTLB Invalidation Request Granularity *//*Z IOTLB失效粒度（高32位部分索引）。2位*/
#define IAIG        25  /* IOTLB Actual Invalidation Granularity *//*Z IOTLB实际失效粒度（高32位部分索引）。2位*/
#define IAIG_MASK   0x7
#define IP          30  /* Interrupt Pending */
#define FRI         0x8 /* Fault Recording Index *//*Z 错误状态寄存器中指示记录首个错误的寄存器索引的起始位 */
#define FRI_MASK    0xFF    /*Z 记录首个错误的寄存器索引位掩码 */
#define FRO         24      /*Z 能力寄存器中指示首个错误记录寄存器偏移量的起始位 */
#define FRO_MASK    0xFF    /*Z 首个错误记录寄存器偏移量的位掩码(低8位) */
#define FI          12
#define SID_MASK    0xFFFF  /*Z 错误源标识符掩码 */
#define SID_BUS(a)  (MASK(8) & (a >> 8))    /*Z 错误源标识符的bus号 */
#define SID_DEV(a)  (MASK(5) & (a >> 3))    /*Z 错误源标识符的dev号 */
#define SID_FUNC(a) (MASK(3) & a)           /*Z 错误源标识符的func号 */
#define FR_MASK     0xFF    /*Z 错误寄存器中记录的错误原因位掩码。总第96~103位 */
#define FAULT_TYPE  30      /*Z 错误寄存器中记录的导致错误的请求类型指示位。0-写或页请求，1-读或原子操作请求。总第126位 */
#define FAULT       31      /*Z 错误寄存器中标识错误已记录的指示位。总第127位 */
#define NFR         8   /* high word of CAP_REG *//*Z 能力寄存器中指示错误记录寄存器数量的起始位(相对于高32位) */
#define NFR_MASK    0xff    /*Z 错误记录寄存器数量位掩码 */
#define PPF         1       /*Z 错误状态寄存器中是否有等待处理错误的指示位 */
#define PPF_MASK    1       /*Z 是否有等待处理错误的指示位掩码 */
#define PRESENT     1
#define WBF         27  /*Z 全局命令寄存器的刷出write-buffer的指示位 */
#define WBFS        27  /*Z 全局状态寄存器的刷出write-buffer完成指示位。0-完成 */
#define DID         8
#define RW          0x3

#define SAGAW         8     /*Z IOMMU能力寄存器中表示支持的客户地址空间大小的位偏移 */
#define SAGAW_2_LEVEL 0x01  /*Z 支持的客户地址位宽：保留 */
#define SAGAW_3_LEVEL 0x02  /*Z 支持的客户地址位宽：39位（3级页表）*/
#define SAGAW_4_LEVEL 0x04  /*Z 支持的客户地址位宽：48位（4级页表）*/
#define SAGAW_5_LEVEL 0x08  /*Z 支持的客户地址位宽：57位（5级页表）*/
#define SAGAW_6_LEVEL 0x10  /*Z 支持的客户地址位宽：保留 */

#define CONTEXT_GLOBAL_INVALIDATE 0x1   /*Z 全部失效上下文缓存指示位模式，01 */
#define IOTLB_GLOBAL_INVALIDATE   0x1   /*Z 全部失效IOTLB指示位模式，01 */

#define DMA_TLB_READ_DRAIN  BIT(17)     /*Z IOTLB失效前排干读操作 */
#define DMA_TLB_WRITE_DRAIN BIT(16)     /*Z IOTLB失效前排干写操作 */

#define N_VTD_CONTEXTS 256  /*Z 老旧设备映射需要的上下文表数量，每个4K */
/*Z IOMMU（DMA重映射硬件单元）索引 */
typedef uint32_t drhu_id_t;
/*Z 读第drhu_id个IOMMU的偏移量offset处的内存，返回u32 */
static inline uint32_t vtd_read32(drhu_id_t drhu_id, uint32_t offset)
{
    return *(volatile uint32_t *)(PPTR_DRHU_START + (drhu_id << PAGE_BITS) + offset);
}
/*Z 写第drhu_id个IOMMU的offset寄存器值value */
static inline void vtd_write32(drhu_id_t drhu_id, uint32_t offset, uint32_t value)
{
    *(volatile uint32_t *)(PPTR_DRHU_START + (drhu_id << PAGE_BITS) + offset) = value;
}

/*Z 读第drhu_id个IOMMU的偏移量offset处的内存，返回u64 */
static inline uint64_t vtd_read64(drhu_id_t drhu_id, uint32_t offset)
{
    return *(volatile uint64_t *)(PPTR_DRHU_START + (drhu_id << PAGE_BITS) + offset);
}
/*Z 写第drhu_id个IOMMU的offset寄存器值value */
static inline void vtd_write64(drhu_id_t drhu_id, uint32_t offset, uint64_t value)
{
    *(volatile uint64_t *)(PPTR_DRHU_START + (drhu_id << PAGE_BITS) + offset) = value;
}
/*Z 获取第drhu_id个IOMMU的失效地址寄存器的偏移量 */
static inline uint32_t get_ivo(drhu_id_t drhu_id)
{
    return ((vtd_read32(drhu_id, ECAP_REG) >> 8) & IVO_MASK) * 16;
}
/*Z 获取第drhu_id个IOMMU的首个错误记录寄存器的偏移量 */
static uint32_t get_fro_offset(drhu_id_t drhu_id)
{
    uint32_t fro_offset;

    /* Get bits 31 to 24 from lower Capability Register */
    fro_offset = (vtd_read32(drhu_id, CAP_REG) >> FRO) & FRO_MASK;

    /* Get bits 33 to 32 from higher Capability Register */
    fro_offset |= (vtd_read32(drhu_id, CAP_REG + 4) & 0x3) << 8;

    return fro_offset << 4;
}
/*Z 失效所有IOMMU的上下文缓存 */
void invalidate_context_cache(void)
{
    /* FIXME - bugzilla bug 172
     * 1. Instead of assuming global invalidation, this function should
     *    accept a parameter to control the granularity of invalidation
     *    request.
     * 2. Instead of doing invalidation for all the IOMMUs, it should
     *    only do it for the IOMMU responsible for the requesting PCI
     *    device.
     */

    drhu_id_t i;

    for (i = 0; i < x86KSnumDrhu; i++) {
        /* Wait till ICC bit is clear */
        uint64_t ccmd = 0;
        while ((vtd_read64(i, CCMD_REG) >> ICC) & 1);

        /* Program CIRG for Global Invalidation by setting bit 61 which
         * will be bit 29 in upper 32 bits of CCMD_REG
         */
        ccmd = ((uint64_t)CONTEXT_GLOBAL_INVALIDATE << CIRG) | (1ull << ICC);

        /* Invalidate Context Cache */
        vtd_write64(i, CCMD_REG, ccmd);

        /* Wait for the invalidation to complete */
        while ((vtd_read64(i, CCMD_REG) >> ICC) & 1);
    }
}
/*Z 失效所有IOMMU的IOTLB */
void invalidate_iotlb(void)
{
    /* FIXME - bugzilla bug 172
     * 1. Instead of assuming global invalidation, this function should
     *    accept a parameter to control the granularity of invalidation
     *    request.
     * 2. Instead of doing invalidation for all the IOMMUs, it should
     *    only do it for the IOMMU responsible for the requesting PCI
     *    device.
     */

    uint8_t   invalidate_command = IOTLB_GLOBAL_INVALIDATE;
    uint32_t  iotlb_reg_upper;
    uint32_t  ivo_offset;
    drhu_id_t i;

    for (i = 0; i < x86KSnumDrhu; i++) {
        ivo_offset = get_ivo(i);/*Z 失效地址寄存器的偏移量 */

        /* Wait till IVT bit is clear */
        while ((vtd_read32(i, ivo_offset + IOTLB_REG + 4) >> IVT) & 1);

        /* Program IIRG for Global Invalidation by setting bit 60 which
         * will be bit 28 in upper 32 bits of IOTLB_REG
         */
        iotlb_reg_upper = invalidate_command << IIRG;

        /* Invalidate IOTLB */
        iotlb_reg_upper |= BIT(IVT);
        iotlb_reg_upper |= DMA_TLB_READ_DRAIN | DMA_TLB_WRITE_DRAIN;

        vtd_write32(i, ivo_offset + IOTLB_REG, 0);/*Z 失效寄存器的低32位是保留的 */
        vtd_write32(i, ivo_offset + IOTLB_REG + 4, iotlb_reg_upper);

        /* Wait for the invalidation to complete */
        while ((vtd_read32(i, ivo_offset + IOTLB_REG + 4) >> IVT) & 1);
    }
}
/*Z 清除第i个IOMMU的fr_reg错误记录寄存器内容 */
static void vtd_clear_fault(drhu_id_t i, word_t fr_reg)
{
    /* Clear the 'F' (Fault) bit to indicate that this fault is processed */
    vtd_write32(i, fr_reg + 12, BIT(FAULT));/*Z 这个位的特殊用法，写1清除整个128位错误记录寄存器值 */
}
/*Z 处理IOMMU错误。仅仅是打印和清标志，由此看出IOMMU不能动态调整分配资源 */
static void vtd_process_faults(drhu_id_t i)
{
    /* Fault Recording register offset relative to the base register */
    uint32_t fro_offset;
    uint32_t source_id UNUSED;
    uint32_t fault_type UNUSED;
    uint32_t address[2] UNUSED;
    uint32_t reason UNUSED;
    uint32_t num_fault_regs;
    uint32_t fr_reg;
    uint32_t fault_status;
    uint32_t fault_record_index;
    /*Z 获取首个错误记录寄存器的偏移量 */
    /* Retrieves FRO by looking into Capability register bits 33 to 24 */
    fro_offset = get_fro_offset(i);
    fault_status = (vtd_read32(i, FSTS_REG) >> PPF) & PPF_MASK;
    /*Z 如果有错误，就处理 */
    if (fault_status) {
        num_fault_regs = ((vtd_read32(i, CAP_REG + 4) >> NFR) & NFR_MASK) + 1;/*Z 错误记录寄存器数量 */
        fault_record_index = (vtd_read32(i, FSTS_REG) >> FRI) & FRI_MASK;
        fr_reg = fro_offset + 16 * fault_record_index;/*Z 记录首个错误的寄存器偏移量 */
        /*Z 处理所有的错误。错误记录寄存器是连续的 */
        /* Traverse the fault register ring buffer */
        do {
            source_id = vtd_read32(i, fr_reg + 8) & SID_MASK;

            fault_type = (vtd_read32(i, fr_reg + 12) >> FAULT_TYPE) & 1;/*Z 错误类型 */
            address[1] = vtd_read32(i, fr_reg + 4);/*Z 如果是页错误，这里和下一个是页地址 */
            address[0] = vtd_read32(i, fr_reg);
            reason = vtd_read32(i, fr_reg + 12) & FR_MASK;/*Z 错误原因 */
            /*Z 报告错误。不好：如果不是地址翻译错误，这地址的值就不是地址 */
            printf("IOMMU: DMA %s page fault ", fault_type ? "read" : "write");
            printf("from 0x%x (bus: 0x%lx/dev: 0x%lx/fun: 0x%lx) ", source_id,
                   SID_BUS(source_id), SID_DEV(source_id), SID_FUNC(source_id));
            printf("on address 0x%x:%x ", address[1], address[0]);
            printf("with reason code 0x%x\n", reason);
            /*Z 清除错误标记 */
            vtd_clear_fault(i, fr_reg);

            fault_record_index = (fault_record_index + 1) % num_fault_regs;
            fr_reg = fro_offset + 16 * fault_record_index;/*Z 下一个错误记录寄存器 */
        } while ((vtd_read32(i, fr_reg + 12) >> FAULT) & 1);

        /* Check for Primary Fault Overflow */
        if (vtd_read32(i, FSTS_REG) & 1) {/*Z 第0位是错误溢出标记。溢出后硬件不再记录新错误 */
            /* Clear PFO bit, so new faults will be generated again ! */
            vtd_write32(i, FSTS_REG, 1);/*Z 直至软件对此位写1进行清除溢出标记 */
        }
    }
}
/*Z 处理IOMMU错误。仅仅是打印和清标志，由此看出IOMMU不能动态调整分配资源 */
void vtd_handle_fault(void)
{
    drhu_id_t i;

    for (i = 0; i < x86KSnumDrhu; i++) {
        vtd_process_faults(i);
    }
}
/*Z 老旧设备DMA保留区占用的IOMMU页表页数。不好甚至BUG：分配了太多的无用内存，同时可能还有部分未满足 */
BOOT_CODE word_t vtd_get_n_paging(acpi_rmrr_list_t *rmrr_list)
{
    if (x86KSnumDrhu == 0) {
        return 0;
    }
    assert(x86KSnumIOPTLevels > 0);

    word_t size = 1; /* one for the root table *//*Z 根表页数 */
    size += N_VTD_CONTEXTS; /* one for each context *//*Z 上下文表页数 */
    size += rmrr_list->num; /* one for each device *//*Z 老旧DMA保留内存每个设备一页顶级页表。这意味着每个设备都是不同的域 */
    /*Z 如果没有保留区，也要返回根表和上下文表的最大数量 */
    if (rmrr_list->num == 0) {
        return size;
    }
    /*Z 大意是过滤掉相同的区域，涉及PCI和DMA规范??? */
    /* filter the identical regions by pci bus id */
    acpi_rmrr_list_t filtered;
    filtered.entries[0] = rmrr_list->entries[0];
    filtered.num = 1;
    /*Z 不好：过滤掉多少，就有多少顶级页表页是空闲的 */
    for (word_t i = 1; i < rmrr_list->num; i++) {
        if (vtd_get_root_index(rmrr_list->entries[i].device) != /*Z PCI总线号，即根表索引 */
            vtd_get_root_index(filtered.entries[filtered.num - 1].device) &&
            rmrr_list->entries[i].base != filtered.entries[filtered.num - 1].base &&/*Z 相邻比较是因为获取RMRR时同一内存多个设备的情况逐一展开在列表里了 */
            rmrr_list->entries[i].limit != filtered.entries[filtered.num - 1].limit) {
            filtered.entries[filtered.num] = rmrr_list->entries[i];
            filtered.num++;/*Z 不在一条总线上(上下文表不同)，且起止地址不同，则++ */
        }/*Z 过滤掉了什么：同一个上下文表，或至少一个地址相同的 */
    }
    /*Z 不好：对过滤掉的，无法保证不需要额外的页表 */
    for (word_t i = x86KSnumIOPTLevels - 1; i > 0; i--) {
        /* If we are still looking up bits beyond the 32bit of physical
         * that we support then we select entry 0 in the current PT */
        if ((VTD_PT_INDEX_BITS * i + seL4_PageBits) >= 32) {
            size++;/*Z 因为保留内存区地址是32位的 */
        } else {
            for (word_t j = 0; j < filtered.num; j++) {
                v_region_t region = (v_region_t) {
                    .start = filtered.entries[j].base,
                    .end = filtered.entries[j].limit
                };                  /*Z BUG：32- 是个严重的错误，去掉????? */
                size += get_n_paging(region, 32 - (VTD_PT_INDEX_BITS * i + seL4_PageBits));
            }
        }
    }
    return size;
}
/*Z 如果未创建过，则为指定的物理地址在指定的上下文表项、和各级页表中创建表项 */
/* This function is a simplistic duplication of some of the logic
 * in iospace.c
 */
BOOT_CODE static void vtd_map_reserved_page(vtd_cte_t *vtd_context_table, int context_index, paddr_t addr)
{
    int i;
    vtd_pte_t *iopt;    /*Z 页表地址。struct{u64[1]} */
    vtd_pte_t *vtd_pte_slot;/*Z 页表项地址 */
    /* first check for the first page table *//*Z 填充上下文表项，获取一级页表地址 */
    vtd_cte_t *vtd_context_slot = vtd_context_table + context_index;
    if (!vtd_cte_ptr_get_present(vtd_context_slot)) {/*Z 上下文表项尚未建立 */
        iopt = (vtd_pte_t *) it_alloc_paging();/*Z 分配一页一级页表 */
        flushCacheRange(iopt, seL4_IOPageTableBits);
        /*Z 新建上下文表项 */
        *vtd_context_slot = vtd_cte_new(
                                x86KSFirstValidIODomain,  /* Domain ID                              *//*Z 所属域标识符 */
                                true,                     /* RMRR Mapping                           *//*Z 硬件忽略此位。估计seL4利用此位表达某种意思 */
                                x86KSnumIOPTLevels - 2,   /* Address Width                          *//*Z 客户机地址位宽指示 */
                                pptr_to_paddr(iopt),      /* Address Space Root                     *//*Z 一级页表物理地址 */
                                0,                        /* Translation Type                       *//*Z 地址转换类型。0-使用一级页表地址 */
                                true);                    /* Present                                *//*Z 在内存 */
        x86KSFirstValidIODomain++;/*Z 每个设备均属于不同的IOMMU域 */
        flushCacheRange(vtd_context_slot, VTD_CTE_SIZE_BITS);
    } else {                                /*Z 获取上下文表项中指示的一级页表地址 */
        iopt = (vtd_pte_t *)paddr_to_pptr(vtd_cte_ptr_get_asr(vtd_context_slot));
    }
    /* now recursively find and map page tables *//*Z 视情创建各级页表项 */
    for (i = x86KSnumIOPTLevels - 1; i >= 0; i--) {
        uint32_t iopt_index;/*Z 页表项索引 */
        /* If we are still looking up bits beyond the 32bit of physical
         * that we support then we select entry 0 in the current PT */
        if (VTD_PT_INDEX_BITS * i + seL4_PageBits >= 32) {
            iopt_index = 0;
        } else {
            iopt_index = ((addr >> seL4_PageBits) >> (VTD_PT_INDEX_BITS * i)) & MASK(VTD_PT_INDEX_BITS);
        }
        vtd_pte_slot = iopt + iopt_index;
        if (i == 0) {/*Z 末级页表 */
            /* Now put the mapping in */
            *vtd_pte_slot = vtd_pte_new(addr, 1, 1);/*Z 物理地址 可写 可读 */
            flushCacheRange(vtd_pte_slot, VTD_PTE_SIZE_BITS);
        } else {/*Z 其它级页表 */
            if (!vtd_pte_ptr_get_write(vtd_pte_slot)) {/*Z 不可写，也意味着不存在 */
                iopt = (vtd_pte_t *) it_alloc_paging();
                flushCacheRange(iopt, seL4_IOPageTableBits);

                *vtd_pte_slot = vtd_pte_new(pptr_to_paddr(iopt), 1, 1);
                flushCacheRange(vtd_pte_slot, VTD_PTE_SIZE_BITS);
            } else {
                iopt = (vtd_pte_t *)paddr_to_pptr(vtd_pte_ptr_get_addr(vtd_pte_slot));
            }
        }
    }
}
/*Z 为IOMMU根表中的指定项(bus)创建独立的上下文表，并将相应bus上的设备DMA保留内存映射到该表及各页表(需要就创建) */
BOOT_CODE static void vtd_create_context_table(uint8_t bus, acpi_rmrr_list_t *rmrr_list)
{
    word_t i;                            /*Z 分配一页内存用于上下文表 */
    vtd_cte_t *vtd_context_table = (vtd_cte_t *) it_alloc_paging();
    /*Z 刷出指针所指内存区域的各级cache */
    printf("IOMMU: Create VTD context table for PCI bus 0x%x (pptr=%p)\n", bus, vtd_context_table);
    flushCacheRange(vtd_context_table, VTD_CT_SIZE_BITS);/*Z 估计目的是防止之前清零操作未同步到内存 */
    /*Z 创建一个根表表项(每bus一项) */
    x86KSvtdRootTable[bus] =
        vtd_rte_new(
            pptr_to_paddr(vtd_context_table), /* Context Table Pointer *//*Z 上下文表物理地址 */
            true                                           /* Present               *//*Z 在内存 */
        );
    /* map in any RMRR regions */
    for (i = 0; i < rmrr_list->num; i++) {/*Z 对每个DMA保留区 */
        if (vtd_get_root_index(rmrr_list->entries[i].device) == bus) {  /*Z 为属于该bus的 */
            uint32_t addr;                                              /*Z 建立上下文表项和各级页表 */
            for (addr = rmrr_list->entries[i].base; addr < rmrr_list->entries[i].limit; addr += BIT(seL4_PageBits)) {
                vtd_map_reserved_page(vtd_context_table, vtd_get_context_index(rmrr_list->entries[i].device), addr);
            }                                           /*Z 只要设备号、功能号不同，就会新建一个一级页表 */
        }
    }
}
/*Z 设置IOMMU根表寄存器、清除缓存、设置MSI中断，使能DMA重映射 */
BOOT_CODE static bool_t vtd_enable(cpu_id_t cpu_id)
{
    drhu_id_t i;
    uint32_t status = 0;
    /*Z 设置各IOMMU的根表寄存器 */
    for (i = 0; i < x86KSnumDrhu; i++) {
        pptr_t pa = (pptr_t)pptr_to_paddr((void *)x86KSvtdRootTable);
        /*Z 写IOMMU根表地址寄存器。x86KSvtdRootTable地址是4K对齐的，因此写入根表寄存器中的转换表模式域(11,10位)为0，即legacy模式 */
        /* Set the Root Table Register */
        vtd_write64(i, RTADDR_REG, pa);
        status = vtd_read32(i, GSTS_REG);
        status |= BIT(SRTP);/*Z 不好：按手册要求，应该先：Status &= 0x96FFFFFF，清除一次性标志 */
        /* Set SRTP bit in GCMD_REG */
        vtd_write32(i, GCMD_REG, status);

        /* Wait for SRTP operation to complete by polling
         * RTPS bit from GSTS_REG
         */
        while (!((vtd_read32(i, GSTS_REG) >> RTPS) & 1));
    }
    /*Z 失效所有IOMMU的上下文缓存 */
    /* Globally invalidate context cache of all IOMMUs */
    invalidate_context_cache();
    /*Z 失效所有IOMMU的IOTLB */
    /* Globally invalidate IOTLB of all IOMMUs */
    invalidate_iotlb();
    /*Z 设置所有IOMMU的不可恢复错误事件的中断控制(MSI)，使能DMA重映射 */
    for (i = 0; i < x86KSnumDrhu; i++) {
        uint32_t data, addr;

        data = int_iommu;
        addr = apic_get_base_paddr();
        if (!addr) {
            return false;
        }
        addr |= (cpu_id << 12);
        
        vtd_process_faults(i);/*Z 处理IOMMU错误 */
        vtd_write32(i, FECTL_REG, 0);/*Z 清除中断屏蔽掩码 */
        vtd_write32(i, FEDATA_REG, data);/*Z 设置中断向量号（MSI）*/
        vtd_write32(i, FEADDR_REG, addr);/*Z 设置MSI的消息基地址(APIC基地址、目的cpu) */
        vtd_write32(i, FEUADDR_REG, 0);
        status = vtd_read32(i, GSTS_REG);
        status |= BIT(WBF);
        /*flush IOMMU write buffer */
        vtd_write32(i, GCMD_REG, status);/*Z 刷出write-buffer */
        while (((vtd_read32(i, GSTS_REG) >> WBFS) & 1));

        printf("IOMMU 0x%x: enabling...", i);

        status = vtd_read32(i, GSTS_REG);
        status |= BIT(TE);
        /* Enable the DMA translation by setting TE bit in GCMD_REG */
        vtd_write32(i, GCMD_REG, status);/*Z 使能DMA重映射 */

        /* Wait for Translation Enable operation to complete by polling
         * TES bit from GSTS_REG
         */
        while (!((vtd_read32(i, GSTS_REG) >> TES) & 1));

        printf(" enabled\n");
    }
    return true;
}
/*Z 获取所有IOMMU共同支持的最少域数量和页表级数 */
BOOT_CODE bool_t vtd_init_num_iopts(uint32_t num_drhu)
{
    x86KSnumDrhu = num_drhu;
    x86KSFirstValidIODomain = 0;

    if (x86KSnumDrhu == 0) {
        return true;
    }
    /*Z 获取所有IOMMU至少支持的域数量和客户地址位宽 */
    uint32_t aw_bitmask = 0xffffffff;
    /* Start the number of domains at 16 bits *//*Z 这个初值也是最大值 */
    uint32_t  num_domain_id_bits = 16;/*Z 所有IOMMU至少支持的域数量（对数表示）。16是架构支持的最大位数 */
    for (drhu_id_t i = 0; i < x86KSnumDrhu; i++) {
        uint32_t bits_supported = 4 + 2 * (vtd_read32(i, CAP_REG) & 7);/*Z 该IOMMU支持的Domain(一个虚拟机执行环境)的数量 */
        aw_bitmask &= vtd_read32(i, CAP_REG) >> SAGAW;/*Z 计算后该值右侧5位表示客户机地址位宽 */
        printf("IOMMU 0x%x: %d-bit domain IDs supported\n", i, bits_supported);
        if (bits_supported < num_domain_id_bits) {
            num_domain_id_bits = bits_supported;
        }
    }
    /*Z 确定所需的页表级数 */
    x86KSnumIODomainIDBits = num_domain_id_bits;
    UNUSED uint32_t  max_num_iopt_levels;
    if (aw_bitmask & SAGAW_6_LEVEL) {
        max_num_iopt_levels = 6;    /*Z 不好：Intel手册是保留的 */
    } else if (aw_bitmask & SAGAW_5_LEVEL) {
        max_num_iopt_levels = 5;
    } else if (aw_bitmask & SAGAW_4_LEVEL) {
        max_num_iopt_levels = 4;
    } else if (aw_bitmask & SAGAW_3_LEVEL) {
        max_num_iopt_levels = 3;
    } else if (aw_bitmask & SAGAW_2_LEVEL) {
        max_num_iopt_levels = 2;    /*Z 不好：Intel手册是保留的 */
    } else {
        printf("IOMMU: mismatch of supported number of PT levels between IOMMUs\n");
        return false;
    }

    if (aw_bitmask & SAGAW_3_LEVEL) {
        x86KSnumIOPTLevels = 3;
    } else if (aw_bitmask & SAGAW_4_LEVEL) {
        x86KSnumIOPTLevels = 4;
    } else if (aw_bitmask & SAGAW_5_LEVEL) {
        x86KSnumIOPTLevels = 5;
    } else if (aw_bitmask & SAGAW_6_LEVEL) {
        x86KSnumIOPTLevels = 6;    /*Z 不好：Intel手册是保留的 */
    } else if (aw_bitmask & SAGAW_2_LEVEL) {
        x86KSnumIOPTLevels = 2;    /*Z 不好：Intel手册是保留的 */
    } else {
        printf("IOMMU: mismatch of supported number of PT levels between IOMMUs\n");
        return false;
    }

    printf("IOMMU: Using %d page-table levels (max. supported: %d)\n", x86KSnumIOPTLevels, max_num_iopt_levels);
    return true;
}
/*Z 创建IOMMU根表、上下文表、各级页表，将老旧设备DMA保留内存区按每设备一个独立域和一套独立页表，映射到各表中；
设置根表寄存器、清除缓存、设置MSI中断，使能DMA重映射。注意：此函数只能由引导核执行一次 */
BOOT_CODE bool_t vtd_init(cpu_id_t  cpu_id, acpi_rmrr_list_t *rmrr_list)
{
    if (x86KSnumDrhu == 0) {/*Z 无IOMMU */
        return true;
    }
    /*Z 分配IOMMU根表内存 */
    x86KSvtdRootTable = (vtd_rte_t *) it_alloc_paging();
    for (uint32_t bus = 0; bus < N_VTD_CONTEXTS; bus++) {/*Z 创建256个上下文表 */
        vtd_create_context_table(bus, rmrr_list);/*Z 创建页表，并将相应bus上的设备DMA保留内存映射到上下文表和各页表 */
    }

    flushCacheRange(x86KSvtdRootTable, VTD_RT_SIZE_BITS);
    /*Z 设置IOMMU根表寄存器、清除缓存、设置MSI中断，使能DMA重映射 */
    if (!vtd_enable(cpu_id)) {
        return false;
    }
    return true;
}

#endif /* CONFIG_IOMMU */
